package com.ysz.component.encrypt;

import com.ysz.component.constant.MyBatisConst;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.*;

/**
 * @ClassName EncryptInterceptor
 * @Description TODO
 * @Data 14:18
 * @Version 1.0
 * @Author ysz
 */
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class MyBatisEncryptInterceptor implements Interceptor {

    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    /**
     * 是否开启加密
     */
    private Boolean enableFlag;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            if (!enableFlag) {
                return invocation.proceed();
            }
            //获取返回结果
            ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
            MetaObject metaResultSetObject = MetaObject.forObject(resultSetHandler, objectFactory, objectWrapperFactory, reflectorFactory);
            MappedStatement mappedStatement = (MappedStatement) metaResultSetObject.getValue("mappedStatement");
            BoundSql boundSql = (BoundSql) metaResultSetObject.getValue("boundSql");
            MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap) boundSql.getParameterObject();
            List<Class> methodParamClass = getMethodParamClass(parameterObject);
            EncryptResultCodeAnnotation encryptResultCodeAnnotation = getEncryptResultCodeAnnotation(mappedStatement, methodParamClass);
            //放行 获取结果
            Object returnResult = invocation.proceed();
            //方法上有这个注解 且有结果集 需要对结果集中的数据加密
            if (Objects.nonNull(encryptResultCodeAnnotation) && Objects.nonNull(returnResult)) {
                //获取要加密的字段
                String[] fields = encryptResultCodeAnnotation.fieldKey();
                //获取加密的算法
                Class<? extends IEncryptResultCodeStrategy>[] encryptStrategyList = encryptResultCodeAnnotation.encryptStrategy();
                //如果是每个字段都有自己单独的加密策略，则加密字段个数需要跟加密策略数一致
                //如果所有字段都采用一个加密策略，则加密策略==1
                if (fields.length > 0 && encryptStrategyList.length == 1) {
                    processReturnResultOneEncrypt(returnResult, fields, encryptStrategyList);
                } else if (fields.length > 0 && encryptStrategyList.length == fields.length) {
                    processReturnResultMoreEncrypt(returnResult, fields, encryptStrategyList);
                }
            }
            return returnResult;
        } catch (Exception e) {
            e.printStackTrace();
            return invocation.proceed();
        }
    }


    public void processReturnResultOneEncrypt(Object returnResult, String[] fields, Class<? extends IEncryptResultCodeStrategy>[] encryptStrategyList) {
        try {
            IEncryptResultCodeStrategy iEncryptResultCodeStrategy = encryptStrategyList[0].newInstance();
            if (returnResult instanceof List) {
                //返回的结果集有很多
                ((List) returnResult).forEach(li -> encryptField(li, fields, iEncryptResultCodeStrategy));
            } else {
                encryptField(returnResult, fields, iEncryptResultCodeStrategy);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void encryptField(Object value, String[] fields, IEncryptResultCodeStrategy encrypt) {
        MetaObject metaObject = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
        List<String> list = Arrays.asList(fields);
        list.forEach(lis -> {
            Object var = metaObject.getValue(lis);
            if (var instanceof String) {
                metaObject.setValue(lis, encrypt.encryption((String) var));
            }
        });
    }


    public void encryptField(Object value, String field, IEncryptResultCodeStrategy encrypt) {
        MetaObject metaObject = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
        Object var = metaObject.getValue(field);
        if (var instanceof String) {
            metaObject.setValue(field, encrypt.encryption((String) var));
        }
    }


    public void processReturnResultMoreEncrypt(Object returnResult, String[] fields, Class<? extends IEncryptResultCodeStrategy>[] encryptStrategyList) {
        try {
            HashMap<String, Class<? extends IEncryptResultCodeStrategy>> map = new HashMap<>(encryptStrategyList.length);
            for (int i = 0; i < encryptStrategyList.length; i++) {
                map.put(fields[i], encryptStrategyList[i]);
            }
            boolean flag = false;
            if (returnResult instanceof List) {
                flag = true;
            }

            for (Map.Entry<String, Class<? extends IEncryptResultCodeStrategy>> entry : map.entrySet()) {
                try {
                    if (flag) {
                        //返回的结果集有很多
                        ((List) returnResult).forEach(li -> {
                            try {
                                IEncryptResultCodeStrategy iEncryptResultCodeStrategy = entry.getValue().newInstance();
                                encryptField(li, entry.getKey(), iEncryptResultCodeStrategy);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        });
                    } else {
                        IEncryptResultCodeStrategy iEncryptResultCodeStrategy = entry.getValue().newInstance();
                        encryptField(returnResult, entry.getKey(), iEncryptResultCodeStrategy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 按照顺序返回参数的class对象
     *
     * @param parameterObject 接口用到的参数
     * @return 按照顺序返回参数的class对象
     * @author ysz
     * @date 2022/3/9 16:42
     */
    public List<Class> getMethodParamClass(MapperMethod.ParamMap parameterObject) {
        List<Class> list = new ArrayList<>(parameterObject.size());
        for (int i = 1; i <= parameterObject.size(); i++) {
            try {
                Object o = parameterObject.get("param" + i);
                if (Objects.nonNull(o)) {
                    list.add(i - 1, o.getClass());
                }
            } catch (Exception e) {
                // ignore
            }
        }
        return list;
    }


    /**
     * 获取接口方法上的加密注解
     *
     * @author ysz
     * @date 2022/3/9 15:01
     */
    public EncryptResultCodeAnnotation getEncryptResultCodeAnnotation(MappedStatement mappedStatement, List<Class> methodParamClass) {
        //com.ysz.mapper.S1Mapper.selectByPageByCount
        //拿到接口方法id
        String id = mappedStatement.getId();
        String clazzName = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1);
        try {
            Class<?> aClass = Class.forName(clazzName);
            Method method = aClass.getMethod(methodName, (Objects.nonNull(methodParamClass) && methodParamClass.size() > 0)
                    ? methodParamClass.toArray(new Class[0]) : null);
            return method.getAnnotation(EncryptResultCodeAnnotation.class);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        enableFlag = Boolean.parseBoolean(properties.getProperty(MyBatisConst.sql_result_encrypt.getKey()));
    }
}
